package com.ruoyi.system.service.impl;

import cn.hutool.core.util.IdUtil;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.constant.DateFormatContants;
import com.ruoyi.common.constant.OperationType;
import com.ruoyi.common.core.domain.dto.SysOrderDto;
import com.ruoyi.common.core.domain.entity.system.*;
import com.ruoyi.common.core.domain.entity.wsbean.JishiOrderEndChargeMsg;
import com.ruoyi.common.core.domain.entity.wsbean.OrderBeginChargeMsg;
import com.ruoyi.common.core.domain.entity.wsbean.PintuanOrderEndMsg;
import com.ruoyi.common.core.domain.entity.wsbean.TaocanOrderEndChargeMsg;
import com.ruoyi.common.core.domain.model.CreatePintuanOrderReqBody;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.domain.model.WxRefundReqBody;
import com.ruoyi.common.enums.*;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.CacheUtils;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.common.utils.uuid.ShortIdUtils;
import com.ruoyi.handler.socket.MySocketChannelInboundHandler1;
import com.ruoyi.handler.websocket.WebSocketMessageHandler;
import com.ruoyi.manager.WebSocketChannelManager;
import com.ruoyi.pay.service.IWxPayV3Service;
import com.ruoyi.system.mapper.*;
import com.ruoyi.system.service.*;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 小程序订单Service业务层处理
 *
 * @author zjw
 * @date 2023-08-08
 */
@Slf4j
@Service
public class SysOrderServiceImpl implements ISysOrderService {
    @Resource
    private SysOrderMapper sysOrderMapper;

    @Resource
    private SysPintuanOrderMapper sysPintuanOrderMapper;

    @Resource
    private SysJishiMapper sysJishiMapper;

    @Resource
    private SysTaocanMapper sysTaocanMapper;

    @Resource
    private SysScenicMapper sysScenicMapper;
    @Resource
    private SysWharfMapper sysWharfMapper;
    @Resource
    private SysBoatMapper sysBoatMapper;

    @Resource
    private SysPintuanTypeMapper sysPintuanTypeMapper;

    @Value("${miniprogram.payCountdownDuration}")
    private int payCountdownDuration;

    @Resource
    private SysJishiOrderMapper sysJishiOrderMapper;

    @Resource
    private SysTaocanOrderMapper sysTaocanOrderMapper;

    @Resource
    private SysPintuanParentOrderMapper sysPintuanParentOrderMapper;

    @Resource
    private SysPintuanChildrenOrderMapper sysPintuanChildrenOrderMapper;

    @Autowired
    private IUserBoatOrderInfoService userBoatOrderInfoService;

    @Autowired
    private IWxPayV3Service wxPayV3Service;

    @Autowired
    private IScenicOperationDynamicInfoService scenicOperationDynamicInfoService;

    @Autowired
    private ISysJishiOrderService sysJishiOrderService;

    @Autowired
    private ISysTaocanOrderService sysTaocanOrderService;

    @Autowired
    private ISysBoatService sysBoatService;

    private SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm");

    /**
     * 查询小程序订单
     *
     * @param orderNo 小程序订单主键
     * @return 小程序订单
     */
    @Override
    public SysOrder selectSysOrderByOutOrderNo(String orderNo) {
        return sysOrderMapper.selectSysOrderByOutOrderNo(orderNo);
    }

    /**
     * 查询小程序订单列表
     *
     * @param sysOrder 小程序订单
     * @return 小程序订单
     */
    @Override
    public List<SysOrder> selectSysOrderList(SysOrder sysOrder) {
        return sysOrderMapper.selectSysOrderList(sysOrder);
    }

    /**
     * 新增小程序订单
     *
     * @param sysOrder 小程序订单
     * @return 结果
     */
    @Override
    public int insertSysOrder(SysOrder sysOrder) {
        return sysOrderMapper.insertSysOrder(sysOrder);
    }

    /**
     * 修改小程序订单
     *
     * @param sysOrder 小程序订单
     * @return 结果
     */
    @Override
    public int updateSysOrder(SysOrder sysOrder) {
        return sysOrderMapper.updateSysOrder(sysOrder);
    }

    /**
     * 批量删除小程序订单
     *
     * @param orderNos 需要删除的小程序订单主键
     * @return 结果
     */
    @Override
    public int deleteSysOrderByOrderNos(String[] orderNos) {
//        return sysOrderMapper.deleteSysOrderByOrderNos(orderNos);
        return 0;
    }

    /**
     * 删除小程序订单信息
     *
     * @param orderNo 小程序订单主键
     * @return 结果
     */
    @Override
    public int deleteSysOrderByOrderNo(String orderNo) {
//        return sysOrderMapper.deleteSysOrderByOrderNo(orderNo);
        return 0;
    }

    @Override
    @Transactional
    public Map<String, Object> createPintuanOrder(CreatePintuanOrderReqBody reqBody) {
        //获取当前登录的用户
        LoginUser loginUser = SecurityUtils.getLoginUser();
        Long userId = loginUser.getUserId();

        //获取当前拼团类型允许拼团的最大人数
        SysPintuanType sysPintuanType = sysPintuanTypeMapper.selectSysPintuanTypeByPintuanId(reqBody.getPintuanId());
        if (Objects.isNull(sysPintuanType)) {
            throw new ServiceException(String.format("参与拼团失败，找不到对应的拼团类型[pintuanId=%s]", reqBody.getPintuanId()));
        }
        int allowMaxPersonNum = sysPintuanType.getPersonNum();

        //判断请求拼团人数是否超过当前拼团类型允许的最大拼团人数
        int applyPintuanPersonNum = reqBody.getPintuanPersonNum();
        if (applyPintuanPersonNum > allowMaxPersonNum) {
            throw new ServiceException("参与拼团失败，请求拼团人数超过当前允许的最大拼团人数");
        }

        //先去redis中查询指定boatId的船只当前是否正在拼团中
        Long boatId = reqBody.getBoatId();
        long expireTime = CacheUtils.getExpire(CacheConstants.Pintuan_Order_Redis_Key, String.valueOf(boatId));
        if (expireTime > 0) {
            //当前船只正在拼团中，当前用户非此拼团订单的发起人
            UserBoatOrderInfo userBoatOrderInfo = userBoatOrderInfoService.selectBoatOrderInfoByBoatId(boatId);
            SysPintuanParentOrder sysPintuanParentOrder = sysPintuanParentOrderMapper.selectSysPintuanParentOrderByOrderId(userBoatOrderInfo.getOrderId());

            //1，先检查请求拼团人数是否超过当前允许的最大拼团人数
            //获取当前拼团中的订单已实际参与拼团的人数
            int currPintuanPersonNum = sysPintuanParentOrder.getPintuanPersonNum();
            if (applyPintuanPersonNum + currPintuanPersonNum > allowMaxPersonNum) {
                throw new ServiceException("参与拼团失败，请求拼团人数超过当前允许的最大拼团人数");
            }
        }

        //为当前用户创建拼团子订单
        SysPintuanChildrenOrder sysPintuanChildrenOrder = new SysPintuanChildrenOrder();
        BeanUtils.copyProperties(reqBody, sysPintuanChildrenOrder);

        //设置订单id
        String orderId = ShortIdUtils.generateOrderIdStr();
        sysPintuanChildrenOrder.setOrderId(orderId);

        //设置订单类型为拼团订单
        sysPintuanChildrenOrder.setOrderType(2);

        //设置当前订单所属的用户id
        sysPintuanChildrenOrder.setUserId(userId);

        //设置订单状态
        sysPintuanChildrenOrder.setOrderStatus(OrderStatus.Unpaid.getCode());
        sysPintuanChildrenOrder.setOrderStatusDesc(OrderStatus.Unpaid.getInfo());

        //设置拼团状态
        sysPintuanChildrenOrder.setPintuanStatus(PintuanStatus.WaitingToStart.getCode());
        sysPintuanChildrenOrder.setPintuanStatusDesc(PintuanStatus.WaitingToStart.getInfo());

        //设置订单的创建时间
        sysPintuanChildrenOrder.setCreateTime(DateUtils.getNowDate());

        //设置支付押金及费用的商户订单号。
        String depositAndFeeOutOrderNo = IdUtil.simpleUUID();
        sysPintuanChildrenOrder.setDepositAndFeeOutOrderNo(depositAndFeeOutOrderNo);

        //设置订单押金及费用相关信息
        sysPintuanChildrenOrder.setOrderDeposit(sysPintuanType.getDeposit() * (reqBody.getPintuanPersonNum()));
        sysPintuanChildrenOrder.setDepositPayStatus(PayStatus.Unpaid.getCode());
        sysPintuanChildrenOrder.setDepositPayStatusDesc(PayStatus.Unpaid.getInfo());
        sysPintuanChildrenOrder.setOrderFee(sysPintuanType.getPrice() * (reqBody.getPintuanPersonNum()));
        sysPintuanChildrenOrder.setFeePayStatus(PayStatus.Unpaid.getCode());
        sysPintuanChildrenOrder.setFeePayStatusDesc(PayStatus.Unpaid.getInfo());

        //设置订单总金额(订单费用+押金)，单位：分。
        sysPintuanChildrenOrder.setOrderTotalPrice(sysPintuanChildrenOrder.getOrderDeposit() + sysPintuanChildrenOrder.getOrderFee());
        //设置用户实际支付的订单总金额(订单费用+押金)，单位：分
        sysPintuanChildrenOrder.setRealPayTotalPrice(0);
        //设置订单文本描述，示例：计时计费订单。
        sysPintuanChildrenOrder.setOrderDesc("拼团订单");

        //设置拼团相关信息
        sysPintuanChildrenOrder.setPintuanDuration(sysPintuanType.getDuration());
        sysPintuanChildrenOrder.setPintuanPrice(sysPintuanType.getPrice());
        sysPintuanChildrenOrder.setPintuanDeposit(sysPintuanType.getDeposit());
        sysPintuanChildrenOrder.setPintuanAllowPersonNum(sysPintuanType.getPersonNum());

        //设置计时相关信息
        SysJishi sysJishi = sysJishiMapper.selectSysJishiByScenicId(reqBody.getScenicId());
        sysPintuanChildrenOrder.setJishiId(sysJishi.getJishiId());
        sysPintuanChildrenOrder.setJishiPrice(sysJishi.getPrice());
        sysPintuanChildrenOrder.setJishiUnit(sysJishi.getUnit());

        //设置景区，码头，船只相关信息
        SysScenic sysScenic = sysScenicMapper.selectSysScenicById(reqBody.getScenicId());
        SysWharf sysWharf = sysWharfMapper.selectWharfByWharfId(reqBody.getWharfId());
        SysBoat sysBoat = sysBoatMapper.selectSysBoatByBoatId(reqBody.getBoatId());
        sysPintuanChildrenOrder.setScenicName(sysScenic.getScenicName());
        sysPintuanChildrenOrder.setWharfName(sysWharf.getWharfName());
        sysPintuanChildrenOrder.setBoatCode(sysBoat.getBoatCode());

        //将sysPintuanChildrenOrder插入数据库
        sysPintuanChildrenOrderMapper.insertSysPintuanChildrenOrder(sysPintuanChildrenOrder);

        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("orderId", orderId);

        resultMap.put("orderStatus", sysPintuanChildrenOrder.getOrderStatus());
        resultMap.put("orderStatusDesc", sysPintuanChildrenOrder.getOrderStatusDesc());
        //订单押金金额。
        resultMap.put("orderDeposit", sysPintuanChildrenOrder.getOrderDeposit());
        //订单押金支付状态。0：未支付，1：支付中，2：已支付，3：退款中，4：已退款
        resultMap.put("depositPayStatus", sysPintuanChildrenOrder.getDepositPayStatus());
        resultMap.put("depositPayStatusDesc", sysPintuanChildrenOrder.getDepositPayStatusDesc());
        //订单费用金额，单位：分。
        resultMap.put("orderFee", sysPintuanChildrenOrder.getOrderFee());
        //订单费用支付状态。0：未支付，1：支付中，2：已支付，3：退款中，4：已退款
        resultMap.put("feePayStatus", sysPintuanChildrenOrder.getFeePayStatus());
        resultMap.put("feePayStatusDesc", sysPintuanChildrenOrder.getFeePayStatusDesc());
        //订单总金额(订单费用+押金)，单位：分。
        resultMap.put("orderTotalPrice", sysPintuanChildrenOrder.getOrderTotalPrice());
        //用户实际支付的订单总金额(订单费用+押金)，单位：分
        resultMap.put("realPayTotalPrice", sysPintuanChildrenOrder.getRealPayTotalPrice());

        //设置订单支付时限(单位：秒)，新创建的订单超过指定时限仍未支付则会被系统主动关闭。
        resultMap.put("remainPayTime", payCountdownDuration);

        //将当前待支付的订单缓存到redis中，并设置有效期，当有效期超时时主动关闭此订单
        CacheUtils.put(CacheConstants.Order_Pay_Status_Redis_Key, orderId + ":" + 2, "", payCountdownDuration, TimeUnit.SECONDS);

        return resultMap;
    }

    /**
     * 创建套餐付费订单。
     *
     * @param sysTaocanOrder
     * @return
     */
    @Override
    @Transactional
    public Map<String, Object> createTaocanOrder(SysTaocanOrder sysTaocanOrder) throws Exception {
        //获取当前登录的用户
        LoginUser loginUser = SecurityUtils.getLoginUser();
        //设置当前订单所属的用户id
        sysTaocanOrder.setUserId(loginUser.getUserId());

        //设置订单所属的景区名称，码头名称和船只编号
        SysScenic sysScenic = sysScenicMapper.selectSysScenicById(sysTaocanOrder.getScenicId());
        SysWharf sysWharf = sysWharfMapper.selectWharfByWharfId(sysTaocanOrder.getWharfId());
        SysBoat sysBoat = sysBoatMapper.selectSysBoatByBoatId(sysTaocanOrder.getBoatId());
        sysTaocanOrder.setScenicName(sysScenic.getScenicName());
        sysTaocanOrder.setWharfName(sysWharf.getWharfName());
        sysTaocanOrder.setBoatCode(sysBoat.getBoatCode());

        //设置订单类型为套餐计费
        sysTaocanOrder.setOrderType(1);

        //设置订单id
        String orderId = ShortIdUtils.generateOrderIdStr();
        sysTaocanOrder.setOrderId(orderId);

        //设置支付押金及费用的商户订单号。
        String depositAndFeeOutOrderNo = IdUtil.simpleUUID();
        sysTaocanOrder.setDepositAndFeeOutOrderNo(depositAndFeeOutOrderNo);

        //设置订单状态。0：待付款，1：待使用，2：使用中，3: 已关闭，4：已完成
        //订单新建时，不管创建的是哪种类型的订单，其初始订单状态都是0，即待付款。
        sysTaocanOrder.setOrderStatus(OrderStatus.Unpaid.getCode());
        sysTaocanOrder.setOrderStatusDesc(OrderStatus.Unpaid.getInfo());
        //设置订单创建时间
        sysTaocanOrder.setCreateTime(DateUtils.getNowDate());

        //设置计时计费相关信息，用于计算后续可能存在的超时费用
        SysJishi sysJishi = sysJishiMapper.selectSysJishiByScenicId(sysTaocanOrder.getScenicId());
        if (Objects.isNull(sysJishi)) {
            throw new ServiceException(String.format("当前景区[scenicId=%s]未设置计时计费规则，订单创建失败!", sysTaocanOrder.getScenicId()));
        }
        sysTaocanOrder.setJishiId(sysJishi.getJishiId());
        sysTaocanOrder.setJishiPrice(sysJishi.getPrice());
        sysTaocanOrder.setJishiUnit(sysJishi.getUnit());

        //设置套餐相关信息
        SysTaocan sysTaocan = sysTaocanMapper.selectSysTaocanByTaocanId(sysTaocanOrder.getTaocanId());
        if (Objects.isNull(sysTaocan)) {
            throw new ServiceException(String.format("找不到对应的套餐[taocanId=%s]，订单创建失败!", sysTaocanOrder.getTaocanId()));
        }

        //设置套餐包含时长
        sysTaocanOrder.setTaocanDuration(sysTaocan.getDuration());
        sysTaocanOrder.setTaocanPrice(sysTaocan.getPrice());

        //设置套餐超市计费规则，单位：元/分钟。
        sysTaocanOrder.setOvertimeFeeRule(sysTaocan.getOvertimeFeeRule());

        //设置订单押金金额。
        sysTaocanOrder.setOrderDeposit(sysTaocan.getDeposit());
        //设置订单押金支付状态。0：未支付，1：支付中，2：已支付，3：退款中，4：已退款
        sysTaocanOrder.setDepositPayStatus(PayStatus.Unpaid.getCode());
        sysTaocanOrder.setDepositPayStatusDesc(PayStatus.Unpaid.getInfo());
        //设置订单费用(即套餐价格)金额，单位：分。
        sysTaocanOrder.setOrderFee(sysTaocan.getPrice());
        //设置订单费用支付状态。0：未支付，1：支付中，2：已支付，3：退款中，4：已退款
        sysTaocanOrder.setFeePayStatus(PayStatus.Unpaid.getCode());
        sysTaocanOrder.setFeePayStatusDesc(PayStatus.Unpaid.getInfo());
        //设置订单总金额(订单费用+押金)，单位：分。
        sysTaocanOrder.setOrderTotalPrice(sysTaocan.getDeposit() + sysTaocan.getPrice());
        //设置用户实际支付的订单总金额(订单费用+押金)，单位：分
        sysTaocanOrder.setRealPayTotalPrice(Double.valueOf(0));
        //设置订单文本描述，示例：计时计费订单。
        sysTaocanOrder.setOrderDesc("套餐计费订单");

        sysTaocanOrderMapper.insertSysTaocanOrder(sysTaocanOrder);

        SysUser sysUser = loginUser.getUser();

        //创建船只与订单信息关联对象
        UserBoatOrderInfo userBoatOrderInfo = new UserBoatOrderInfo();
        userBoatOrderInfo.setUserId(loginUser.getUserId());
        if (Objects.nonNull(sysUser)) {
            userBoatOrderInfo.setUserPhone(sysUser.getPhonenumber());
        }
        userBoatOrderInfo.setBoatId(sysBoat.getBoatId());
        userBoatOrderInfo.setBoatIdentityId(sysBoat.getBoatIdentityId());
        userBoatOrderInfo.setOrderId(sysTaocanOrder.getOrderId());
        userBoatOrderInfo.setOrderType(sysTaocanOrder.getOrderType());
        userBoatOrderInfo.setOrderStatus(OrderStatus.Unpaid.getCode());
        userBoatOrderInfo.setOrderStatusDesc(OrderStatus.Unpaid.getInfo());
        userBoatOrderInfo.setUserId(sysTaocanOrder.getUserId());
        //如果同一个用户短时间内多次重复创建订单，则也会多次调用此行代码，因此会触发DuplicateKeyException异常，即主键重复异常
        userBoatOrderInfoService.insertBoatOrderInfo(userBoatOrderInfo);

        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("orderId", orderId);

        resultMap.put("orderType", sysTaocanOrder.getOrderType());

        resultMap.put("orderStatus", sysTaocanOrder.getOrderStatus());
        resultMap.put("orderStatusDesc", sysTaocanOrder.getOrderStatusDesc());
        //订单押金金额。
        resultMap.put("orderDeposit", sysTaocanOrder.getOrderDeposit());
        //订单押金支付状态。0：未支付，1：支付中，2：已支付，3：退款中，4：已退款
        resultMap.put("depositPayStatus", sysTaocanOrder.getDepositPayStatus());
        resultMap.put("depositPayStatusDesc", sysTaocanOrder.getDepositPayStatusDesc());
        //订单费用金额，单位：分。
        resultMap.put("orderFee", sysTaocanOrder.getOrderFee());
        //订单费用支付状态。0：未支付，1：支付中，2：已支付，3：退款中，4：已退款
        resultMap.put("feePayStatus", sysTaocanOrder.getFeePayStatus());
        resultMap.put("feePayStatusDesc", sysTaocanOrder.getFeePayStatusDesc());
        //订单总金额(订单费用+押金)，单位：分。
        resultMap.put("orderTotalPrice", sysTaocanOrder.getOrderTotalPrice());
        //用户实际支付的订单总金额(订单费用+押金)，单位：分
        resultMap.put("realPayTotalPrice", sysTaocanOrder.getRealPayTotalPrice());

        //设置订单支付时限(单位：秒)，新创建的订单超过指定时限仍未支付则会被系统主动关闭。
        resultMap.put("remainPayTime", payCountdownDuration);

        //将当前待支付的订单缓存到redis中，并设置有效期，当有效期超时时主动关闭此订单
        CacheUtils.put(CacheConstants.Order_Pay_Status_Redis_Key, orderId + ":" + 1, "", payCountdownDuration, TimeUnit.SECONDS);

        //更新当前船只状态为"支付倒计时中"
        sysBoat.setStatus(BoatStatus.PayCounting.getCode());
        sysBoat.setStatusDesc(BoatStatus.PayCounting.getInfo());
        sysBoatMapper.updateSysBoat(sysBoat);

        //创建运营动态信息
        ScenicOperationDynamicInfo scenicOperationDynamicInfo = new ScenicOperationDynamicInfo();
        scenicOperationDynamicInfo.setOperationType(OperationType.Order_Created);
        scenicOperationDynamicInfo.setScenicId(sysTaocanOrder.getScenicId());
        scenicOperationDynamicInfo.setBoatId(sysTaocanOrder.getBoatId());
        scenicOperationDynamicInfo.setBoatCode(sysTaocanOrder.getBoatCode());
        scenicOperationDynamicInfo.setBoatIdentityId(userBoatOrderInfo.getBoatIdentityId());
        scenicOperationDynamicInfo.setOrderId(orderId);
        scenicOperationDynamicInfo.setOrderType(1);
        scenicOperationDynamicInfo.setUserId(sysTaocanOrder.getUserId());
        Date createTime = new Date();
        scenicOperationDynamicInfo.setCreateTime(createTime);
        scenicOperationDynamicInfo.setContent(String.format("%s, %s船新建一笔套餐计费订单!", DateFormatContants.Format1.format(createTime), sysTaocanOrder.getBoatCode()));
        scenicOperationDynamicInfoService.insertOperationInfo(scenicOperationDynamicInfo);

        //向景区监控中心发送运营动态信息
        WebSocketMessageHandler.sendMsgToScenicAdmin(sysTaocanOrder.getScenicId(), scenicOperationDynamicInfo);

        return resultMap;
    }

    @Override
    @Transactional
    public Map<String, Object> createJishiOrder(SysJishiOrder sysJishiOrder) throws Exception {
        //获取当前登录的用户
        LoginUser loginUser = SecurityUtils.getLoginUser();
        //设置当前订单所属的用户id
        sysJishiOrder.setUserId(loginUser.getUserId());

        //设置订单所属的景区名称，码头名称和船只编号
        SysScenic sysScenic = sysScenicMapper.selectSysScenicById(sysJishiOrder.getScenicId());
        SysWharf sysWharf = sysWharfMapper.selectWharfByWharfId(sysJishiOrder.getWharfId());
        SysBoat sysBoat = sysBoatMapper.selectSysBoatByBoatId(sysJishiOrder.getBoatId());
        sysJishiOrder.setScenicName(sysScenic.getScenicName());
        sysJishiOrder.setWharfName(sysWharf.getWharfName());
        sysJishiOrder.setBoatCode(sysBoat.getBoatCode());

        //设置订单类型为计时计费
        sysJishiOrder.setOrderType(0);

        //设置订单id
        String orderId = ShortIdUtils.generateOrderIdStr();
        sysJishiOrder.setOrderId(orderId);

        //设置押金商户订单号。
        String depositOutOrderNo = IdUtil.simpleUUID();
        sysJishiOrder.setDepositOutOrderNo(depositOutOrderNo);

        //设置订单状态。0：待付款，1：待使用，2：使用中，3: 已关闭，4：已完成
        //订单新建时，不管创建的是哪种类型的订单，其初始订单状态都是0，即待付款。
        sysJishiOrder.setOrderStatus(OrderStatus.Unpaid.getCode());
        sysJishiOrder.setOrderStatusDesc(OrderStatus.Unpaid.getInfo());
        //设置订单创建时间
        sysJishiOrder.setCreateTime(DateUtils.getNowDate());

        SysJishi sysJishi = sysJishiMapper.selectSysJishiByJishiId(sysJishiOrder.getJishiId());
        if (Objects.isNull(sysJishi)) {
            throw new ServiceException(String.format("找不到对应的计时计费规则[jishiId=%s]，订单创建失败!", sysJishiOrder.getJishiId()));
        }

        //设置订单押金金额。
        sysJishiOrder.setOrderDeposit(sysJishi.getDeposit());
        //设置订单押金支付状态。0：未支付，1：支付中，2：已支付，3：退款中，4：已退款
        sysJishiOrder.setDepositPayStatus(PayStatus.Unpaid.getCode());
        sysJishiOrder.setDepositPayStatusDesc(PayStatus.Unpaid.getInfo());
        //设置订单费用金额，单位：分。
        sysJishiOrder.setOrderFee(Double.valueOf(0));
        //设置订单费用支付状态。0：未支付，1：支付中，2：已支付，3：退款中，4：已退款
        sysJishiOrder.setFeePayStatus(PayStatus.Unpaid.getCode());
        sysJishiOrder.setFeePayStatusDesc(PayStatus.Unpaid.getInfo());
        //设置订单总金额(订单费用+押金)，单位：分。
        sysJishiOrder.setOrderTotalPrice(sysJishi.getDeposit());
        //设置用户实际支付的订单总金额(订单费用+押金)，单位：分
        sysJishiOrder.setRealPayTotalPrice(Double.valueOf(0));
        //设置订单文本描述，示例：计时计费订单。
        sysJishiOrder.setOrderDesc("计时计费订单");
        sysJishiOrderMapper.insertSysJishiOrder(sysJishiOrder);

        SysUser sysUser = loginUser.getUser();

        //创建船只与订单信息关联对象
        UserBoatOrderInfo userBoatOrderInfo = new UserBoatOrderInfo();
        userBoatOrderInfo.setUserId(sysJishiOrder.getUserId());
        if (Objects.nonNull(sysUser)) {
            userBoatOrderInfo.setUserPhone(sysUser.getPhonenumber());
        }
        userBoatOrderInfo.setBoatId(sysBoat.getBoatId());
        userBoatOrderInfo.setBoatIdentityId(sysBoat.getBoatIdentityId());
        userBoatOrderInfo.setOrderId(sysJishiOrder.getOrderId());
        userBoatOrderInfo.setOrderType(sysJishiOrder.getOrderType());
        userBoatOrderInfo.setOrderStatus(OrderStatus.Unpaid.getCode());
        userBoatOrderInfo.setOrderStatusDesc(OrderStatus.Unpaid.getInfo());
        userBoatOrderInfo.setUserId(sysJishiOrder.getUserId());
        //如果同一个用户短时间内多次重复创建订单，则也会多次调用此行代码，因此会触发DuplicateKeyException异常，即主键重复异常
        userBoatOrderInfoService.insertBoatOrderInfo(userBoatOrderInfo);

        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("orderId", orderId);
//        resultMap.put("depositOutOrderNo", depositOutOrderNo);

        resultMap.put("orderStatus", sysJishiOrder.getOrderStatus());
        resultMap.put("orderStatusDesc", sysJishiOrder.getOrderStatusDesc());
        //订单押金金额。
        resultMap.put("orderDeposit", sysJishiOrder.getOrderDeposit());
        //订单押金支付状态。0：未支付，1：支付中，2：已支付，3：退款中，4：已退款
        resultMap.put("depositPayStatus", sysJishiOrder.getDepositPayStatus());
        resultMap.put("depositPayStatusDesc", sysJishiOrder.getDepositPayStatusDesc());
        //订单费用金额，单位：分。
        resultMap.put("orderFee", sysJishiOrder.getOrderFee());
        //订单费用支付状态。0：未支付，1：支付中，2：已支付，3：退款中，4：已退款
        resultMap.put("feePayStatus", sysJishiOrder.getFeePayStatus());
        resultMap.put("feePayStatusDesc", sysJishiOrder.getFeePayStatusDesc());
        //订单总金额(订单费用+押金)，单位：分。
        resultMap.put("orderTotalPrice", sysJishiOrder.getOrderTotalPrice());
        //用户实际支付的订单总金额(订单费用+押金)，单位：分
        resultMap.put("realPayTotalPrice", sysJishiOrder.getRealPayTotalPrice());

        log.info("payCountdownDuration: {}", payCountdownDuration);
        //设置订单支付时限(单位：秒)，新创建的订单超过指定时限仍未支付则会被系统主动关闭。
        resultMap.put("remainPayTime", payCountdownDuration);

        //将当前待支付的订单缓存到redis中，并设置有效期，当有效期超时时主动关闭此订单
        CacheUtils.put(CacheConstants.Order_Pay_Status_Redis_Key, orderId + ":" + 0, "", payCountdownDuration, TimeUnit.SECONDS);

        //更新当前船只状态为"支付倒计时中"
        sysBoat.setStatus(BoatStatus.PayCounting.getCode());
        sysBoat.setStatusDesc(BoatStatus.PayCounting.getInfo());
        sysBoatMapper.updateSysBoat(sysBoat);

        //创建运营动态信息
        ScenicOperationDynamicInfo scenicOperationDynamicInfo = new ScenicOperationDynamicInfo();
        scenicOperationDynamicInfo.setOperationType(OperationType.Order_Created);
        scenicOperationDynamicInfo.setScenicId(sysJishiOrder.getScenicId());
        scenicOperationDynamicInfo.setBoatId(sysJishiOrder.getBoatId());
        scenicOperationDynamicInfo.setBoatCode(sysJishiOrder.getBoatCode());
        scenicOperationDynamicInfo.setBoatIdentityId(userBoatOrderInfo.getBoatIdentityId());
        scenicOperationDynamicInfo.setOrderId(orderId);
        scenicOperationDynamicInfo.setOrderType(0);
        scenicOperationDynamicInfo.setUserId(sysJishiOrder.getUserId());
        Date createTime = new Date();
        scenicOperationDynamicInfo.setCreateTime(createTime);
        scenicOperationDynamicInfo.setContent(String.format("%s, %s船新建一笔计时计费订单!", DateFormatContants.Format1.format(createTime), sysJishiOrder.getBoatCode()));
        scenicOperationDynamicInfoService.insertOperationInfo(scenicOperationDynamicInfo);

        //向景区监控中心发送运营动态信息
        WebSocketMessageHandler.sendMsgToScenicAdmin(sysJishiOrder.getScenicId(), scenicOperationDynamicInfo);

        return resultMap;
    }

    @Override
    public Map<String, Object> queryPayStatus(String outOrderNo) {
        Map<String, Object> resultMap = new HashMap<>();
        SysOrder sysOrder = sysOrderMapper.selectSysOrderByOutOrderNo(outOrderNo);
        if (Objects.isNull(sysOrder)) {
            throw new ServiceException("查询失败，未找到订单:" + outOrderNo);
        }
        int depositPayStatus = sysOrder.getDepositPayStatus();
        int feePayStatus = sysOrder.getFeePayStatus();
        double realPayDeposit = sysOrder.getRealPayDeposit();
        double realPayFee = sysOrder.getRealPayFee();

        resultMap.put("depositPayStatus", depositPayStatus);
        resultMap.put("realPayDeposit", realPayDeposit);
        resultMap.put("feePayStatus", feePayStatus);
        resultMap.put("realPayFee", realPayFee);

        return resultMap;
    }

    @Override
    public Map<String, Object> queryRefundStatus(String outRefundNo, int refundType) {
        Map<String, Object> resultMap = new HashMap<>();
//        SysOrder sysOrder = sysOrderMapper.selectSysOrderByOutRefundNo(outRefundNo);
//        if (Objects.isNull(sysOrder)) {
//            throw new ServiceException("查询失败，未找到订单:" + outRefundNo);
//        }
//
//        if (refundType == 0) { //只退押金
//            int depositPayStatus = sysOrder.getDepositPayStatus();
//            if (depositPayStatus == 3) { //退款中
//                resultMap.put("refundStatus", 0);
//                resultMap.put("refundDesc", "退款中");
//            } else if (depositPayStatus == 4) { //退款失败
//                resultMap.put("refundStatus", 1);
//                resultMap.put("refundDesc", "退款失败");
//            } else if (depositPayStatus == 5) { //已退款
//                resultMap.put("refundStatus", 2);
//                resultMap.put("refundDesc", "已退款");
//            } else {
//                resultMap.put("refundStatus", 3);
//                resultMap.put("refundDesc", "未知");
//            }
//
//        } else if (refundType == 1) { //只退订单费用
//        } else if (refundType == 2) { //退订单全部已支付金额，即押金+费用。
//        } else {
//            throw new ServiceException("退款失败，退款类型参数不符合要求");
//        }

        return resultMap;
    }

    @Override
    public SysOrderDto queryOrder(String outOrderNo) {
        SysOrder sysOrder = sysOrderMapper.selectSysOrderByOutOrderNo(outOrderNo);
        if (Objects.isNull(sysOrder)) {
            throw new ServiceException("找不到指定订单.outOrderNo: " + outOrderNo);
        }
        SysOrderDto orderDto = new SysOrderDto();
        BeanUtils.copyProperties(sysOrder, orderDto);
        SysScenic sysScenic = sysScenicMapper.selectSysScenicById(sysOrder.getScenicId());
        SysWharf sysWharf = sysWharfMapper.selectWharfByWharfId(sysOrder.getWharfId());
        SysBoat sysBoat = sysBoatMapper.selectSysBoatByBoatId(sysOrder.getBoatId());

        orderDto.setScenicName(sysScenic.getScenicName());
        orderDto.setWharfName(sysWharf.getWharfName());
        orderDto.setBoatCode(sysBoat.getBoatCode());

        return orderDto;
    }

    @Override
    public List<SysOrderDto> queryAllOrderByUserId(Long userId) {
        List<SysOrder> sysOrders = sysOrderMapper.selectSysOrderByUserId(userId);
        List<SysOrderDto> orderDtos = sysOrders.stream().map(sysOrder -> {
            SysOrderDto orderDto = new SysOrderDto();
            BeanUtils.copyProperties(sysOrder, orderDto);
            SysScenic sysScenic = sysScenicMapper.selectSysScenicById(sysOrder.getScenicId());
            SysWharf sysWharf = sysWharfMapper.selectWharfByWharfId(sysOrder.getWharfId());
            SysBoat sysBoat = sysBoatMapper.selectSysBoatByBoatId(sysOrder.getBoatId());

            orderDto.setScenicName(sysScenic.getScenicName());
            orderDto.setWharfName(sysWharf.getWharfName());
            orderDto.setBoatCode(sysBoat.getBoatCode());

            return orderDto;
        }).collect(Collectors.toList());
        return orderDtos;
    }

    @Override
    public List<SysOrderDto> queryUnpaidOrder(Long userId) {
        List<SysOrder> sysOrders = sysOrderMapper.queryUnpaidOrder(userId);
        List<SysOrderDto> orderDtos = sysOrders.stream().map(sysOrder -> {
            SysOrderDto orderDto = new SysOrderDto();
            BeanUtils.copyProperties(sysOrder, orderDto);
            SysScenic sysScenic = sysScenicMapper.selectSysScenicById(sysOrder.getScenicId());
            SysWharf sysWharf = sysWharfMapper.selectWharfByWharfId(sysOrder.getWharfId());
            SysBoat sysBoat = sysBoatMapper.selectSysBoatByBoatId(sysOrder.getBoatId());

            orderDto.setScenicName(sysScenic.getScenicName());
            orderDto.setWharfName(sysWharf.getWharfName());
            orderDto.setBoatCode(sysBoat.getBoatCode());

            long remainPayTime = CacheUtils.getExpire(CacheConstants.Order_Pay_Status_Redis_Key, sysOrder.getOutOrderNo());
            orderDto.setRemainPayTime(remainPayTime);

            return orderDto;
        }).collect(Collectors.toList());
        return orderDtos;
    }

    @Override
    public List<SysOrderDto> queryToUseOrder(Long userId) {
        List<SysOrder> sysOrders = sysOrderMapper.queryToUseOrder(userId);
        List<SysOrderDto> orderDtos = sysOrders.stream().map(sysOrder -> {
            SysOrderDto orderDto = new SysOrderDto();
            BeanUtils.copyProperties(sysOrder, orderDto);
            SysScenic sysScenic = sysScenicMapper.selectSysScenicById(sysOrder.getScenicId());
            SysWharf sysWharf = sysWharfMapper.selectWharfByWharfId(sysOrder.getWharfId());
            SysBoat sysBoat = sysBoatMapper.selectSysBoatByBoatId(sysOrder.getBoatId());

            orderDto.setScenicName(sysScenic.getScenicName());
            orderDto.setWharfName(sysWharf.getWharfName());
            orderDto.setBoatCode(sysBoat.getBoatCode());

            //获取扫码确认上船倒计时剩余时长
            long confirmOnBoatRemainTime = CacheUtils.getExpire(CacheConstants.Order_Confirm_On_Boat_Status_Redis_Key, sysOrder.getOutOrderNo());
            orderDto.setConfirmOnBoatRemainTime(confirmOnBoatRemainTime);

            return orderDto;
        }).collect(Collectors.toList());
        return orderDtos;
    }

    @Override
    public List<SysOrderDto> queryUsingOrder(Long userId) {
        List<SysOrder> sysOrders = sysOrderMapper.queryUsingOrder(userId);
        List<SysOrderDto> orderDtos = sysOrders.stream().map(sysOrder -> {
            SysOrderDto orderDto = new SysOrderDto();
            BeanUtils.copyProperties(sysOrder, orderDto);
            SysScenic sysScenic = sysScenicMapper.selectSysScenicById(sysOrder.getScenicId());
            SysWharf sysWharf = sysWharfMapper.selectWharfByWharfId(sysOrder.getWharfId());
            SysBoat sysBoat = sysBoatMapper.selectSysBoatByBoatId(sysOrder.getBoatId());

            orderDto.setScenicName(sysScenic.getScenicName());
            orderDto.setWharfName(sysWharf.getWharfName());
            orderDto.setBoatCode(sysBoat.getBoatCode());

            return orderDto;
        }).collect(Collectors.toList());
        return orderDtos;
    }

    @Override
    public List<SysOrderDto> queryFulfilledOrder(Long userId) {
        List<SysOrder> sysOrders = sysOrderMapper.queryFulfilledOrder(userId);
        List<SysOrderDto> orderDtos = sysOrders.stream().map(sysOrder -> {
            SysOrderDto orderDto = new SysOrderDto();
            BeanUtils.copyProperties(sysOrder, orderDto);
            SysScenic sysScenic = sysScenicMapper.selectSysScenicById(sysOrder.getScenicId());
            SysWharf sysWharf = sysWharfMapper.selectWharfByWharfId(sysOrder.getWharfId());
            SysBoat sysBoat = sysBoatMapper.selectSysBoatByBoatId(sysOrder.getBoatId());

            orderDto.setScenicName(sysScenic.getScenicName());
            orderDto.setWharfName(sysWharf.getWharfName());
            orderDto.setBoatCode(sysBoat.getBoatCode());

            return orderDto;
        }).collect(Collectors.toList());
        return orderDtos;
    }

    public void mockUnlockAndUsingForPintuanOrder(SysPintuanParentOrder sysPintuanParentOrder, Long unlockDelayTime, Long mockUsingTime) {
        //模拟经过指定时间后收到解锁通知
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                //此时船锁已解开，订单开始计费
                Date beginTime = new Date();

                //将船只状态从"开锁等待中"更新为“手动驾驶中” (默认为手动驾驶)
                SysBoat sysBoat = new SysBoat();
                sysBoat.setBoatId(sysPintuanParentOrder.getBoatId());
                sysBoat.setStatus(BoatStatus.ManualDriving.getCode());
                sysBoat.setStatusDesc(BoatStatus.ManualDriving.getInfo());
                sysBoatMapper.updateSysBoat(sysBoat);

                List<SysPintuanChildrenOrder> pintuanChildrenOrderList = sysPintuanParentOrder.getPintuanChildrenOrderList();
                //通知所有参与拼团且已完成扫码的用户订单开始计费
                pintuanChildrenOrderList.forEach(childrenOrder -> {
                    childrenOrder.setBeginTime(beginTime);
                    childrenOrder.setOrderStatus(OrderStatus.Using.getCode());
                    childrenOrder.setOrderStatusDesc(OrderStatus.Using.getInfo());
                    sysPintuanChildrenOrderMapper.updateSysPintuanChildrenOrder(childrenOrder);

                    OrderBeginChargeMsg jishiOrderBeginChargeMsg = new OrderBeginChargeMsg();
                    jishiOrderBeginChargeMsg.setOrderId(childrenOrder.getOrderId());
                    jishiOrderBeginChargeMsg.setOrderType(2);
                    jishiOrderBeginChargeMsg.setBeginTime(beginTime);
                    Channel miniProgramChannel = WebSocketChannelManager.getMiniprogramWebsocketChannelByUserId(childrenOrder.getUserId());
                    if (Objects.nonNull(miniProgramChannel)) {
                        WebSocketMessageHandler.notifyMiniClientOrderBeginCharging(miniProgramChannel, jishiOrderBeginChargeMsg);
                    }
                });

                //模拟使用了指定时长
                new Timer().schedule(new TimerTask() {
                    @Override
                    public void run() {
                        //此时收到船锁上锁成功的通知
                        //订单结束计费的时间
                        Date endTime = new Date();
                        //获取订单实际持续的收费时长，单位：毫秒。
                        long realDuration = endTime.getTime() - beginTime.getTime();
                        //将实际使用时长realDuration(单位：毫秒)换算成分钟(且取整)
                        long realMinute = realDuration / (60 * 1000);
                        //计算超时时长(单位：分钟)
                        long overtime = realMinute - sysPintuanParentOrder.getPintuanDuration();
                        if (overtime <= 0) {
                            overtime = 0;
                        }

                        //此时船只已上锁，因此可以将船只状态从"手动驾驶中"更新为“空闲”
                        SysBoat sysBoat = new SysBoat();
                        sysBoat.setBoatId(sysPintuanParentOrder.getBoatId());
                        sysBoat.setStatus(BoatStatus.Idle.getCode());
                        sysBoat.setStatusDesc(BoatStatus.Idle.getInfo());
                        sysBoatMapper.updateSysBoat(sysBoat);

                        //计算单个人需要补交的超时费用(单位：元)
                        //1,先获取当前景区的计时计费规则
                        double price = sysPintuanParentOrder.getJishiPrice(); //计价：元/分钟
                        double overtimeFeeOfEveryone = price * overtime;

                        List<SysPintuanChildrenOrder> pintuanChildrenOrderList = sysPintuanParentOrder.getPintuanChildrenOrderList();
                        for (SysPintuanChildrenOrder childrenOrder : pintuanChildrenOrderList) {
                            //设置订单停止计费的时间
                            childrenOrder.setEndTime(endTime);
                            //设置订单实际持续的收费时长
                            childrenOrder.setRealDuration(realDuration);

                            PintuanOrderEndMsg pintuanOrderEndMsg = new PintuanOrderEndMsg();
                            pintuanOrderEndMsg.setOrderId(childrenOrder.getOrderId());
                            pintuanOrderEndMsg.setBeginTime(beginTime);
                            pintuanOrderEndMsg.setEndTime(endTime);
                            pintuanOrderEndMsg.setRealDuration(realDuration);
                            pintuanOrderEndMsg.setPintuanDuration(childrenOrder.getPintuanDuration());
                            pintuanOrderEndMsg.setOvertime(overtime);

                            if (realMinute > 0 && overtime > 0) { //订单总计费时长超过1分钟(含) && 套餐订单超时，且超过1分钟(含)以上
                                //将订单状态更新为“待付款”。注意：此时订单状态不能修改为“已完成”，因为此时订单超时费用还未支付，当前只是结束计费了，订单并未完成。
                                childrenOrder.setOrderStatus(OrderStatus.Unpaid.getCode());
                                childrenOrder.setOrderStatusDesc(OrderStatus.Unpaid.getInfo());

                                //设置订单超时费用需支付金额及当前支付状态
                                double overtimeFee = childrenOrder.getPintuanPersonNum() * overtimeFeeOfEveryone;
                                childrenOrder.setOvertimeFee(overtimeFee);
                                childrenOrder.setOvertimeFeePayStatus(PayStatus.Unpaid.getCode());
                                childrenOrder.setOvertimeFeePayStatusDesc(PayStatus.Unpaid.getInfo());

                                sysPintuanChildrenOrderMapper.updateSysPintuanChildrenOrder(childrenOrder);

                                //通知小程序客户端停止计费。
                                pintuanOrderEndMsg.setOvertimeFee(overtimeFee);
                                Channel miniProgramChannel = WebSocketChannelManager.getMiniprogramWebsocketChannelByUserId(childrenOrder.getUserId());
                                if (Objects.nonNull(miniProgramChannel)) {
                                    WebSocketMessageHandler.notifyClientSidePintuanOrderStopCharging(miniProgramChannel, pintuanOrderEndMsg);
                                }
                            } else {
                                //结束并关闭订单
                                childrenOrder.setOrderStatus(OrderStatus.Fulfilled.getCode());
                                childrenOrder.setOrderStatusDesc(OrderStatus.Fulfilled.getInfo());

                                //此时需要支付的订单费用为0
                                double overtimeFee = 0;
                                childrenOrder.setOvertimeFee(overtimeFee);
                                sysPintuanChildrenOrderMapper.updateSysPintuanChildrenOrder(childrenOrder);

                                //通知小程序客户端停止计费。
                                pintuanOrderEndMsg.setOvertimeFee(overtimeFee);
                                Channel miniProgramChannel = WebSocketChannelManager.getMiniprogramWebsocketChannelByUserId(childrenOrder.getUserId());
                                if (Objects.nonNull(miniProgramChannel)) {
                                    WebSocketMessageHandler.notifyClientSidePintuanOrderStopCharging(miniProgramChannel, pintuanOrderEndMsg);
                                }

                                //订单已完成，退还押金
                                new Timer().schedule(new TimerTask() {
                                    @Override
                                    public void run() {
                                        wxPayV3Service.refund(new WxRefundReqBody(childrenOrder.getOrderId(), 2, 0, "订单已完成，退还押金"));
                                    }
                                }, 5000);

                                //每一次循环都需要检查是否所有已扫码上船的用户都已完成并关闭订单
                                boolean notAllOrderFulfilled = false;
                                for (SysPintuanChildrenOrder childrenOrder1 : pintuanChildrenOrderList) {
                                    if (childrenOrder1.getOnBoatStatus() == 1 && childrenOrder1.getOrderStatus() != OrderStatus.Fulfilled.getCode()) {
                                        //此时说明还有已上船的子订单未完成
                                        notAllOrderFulfilled = true;
                                        break;
                                    }
                                }

                                if (!notAllOrderFulfilled) { //所有已扫码上船的用户都已完成并关闭订单
                                    sysPintuanParentOrder.setOrderStatus(OrderStatus.Fulfilled.getCode());
                                    sysPintuanParentOrder.setOrderStatusDesc(OrderStatus.Fulfilled.getInfo());
                                    sysPintuanParentOrderMapper.updateSysPintuanParentOrder(sysPintuanParentOrder);

                                    //删除船只订单关联对象
                                    userBoatOrderInfoService.deleteUserBoatOrderInfoByUserId(sysPintuanParentOrder.getUserId());
                                }
                            }
                        }
                    }
                }, mockUsingTime);
            }
        }, unlockDelayTime);
    }

    @Override
    public void closeOrder(int orderType, String orderId) {
        if (orderType == 0) { //计时收费订单
            SysJishiOrder sysJishiOrder = sysJishiOrderService.selectSysJishiOrderByOrderId(orderId);
            int orderStatus = sysJishiOrder.getOrderStatus();
            if (orderStatus == 2) { //使用中
                Date beginTime = sysJishiOrder.getBeginTime();
                if (Objects.isNull(beginTime)) { //确保订单还未开始计费
                    //关闭当前使用中的且还未开始计费的订单
                    closeJishiOrder(sysJishiOrder, false);
                }
            } else if (orderStatus == 0) { //待付款
                //判断是否存在待支付的费用，比如计时订单计时费用和套餐订单超时费用
                Double orderFee = sysJishiOrder.getOrderFee();
                if (Objects.nonNull(orderFee) && orderFee >= 0) { //确认当前订单存在待支付的计时订单费用
                    //关闭已结束计费但仍存在待支付费用的订单
                    closeJishiOrder(sysJishiOrder, true);
                }
            }
        } else if (orderType == 1) { //套餐收费订单
            SysTaocanOrder sysTaocanOrder = sysTaocanOrderService.selectSysTaocanOrderByOrderId(orderId);
            int orderStatus = sysTaocanOrder.getOrderStatus();
            if (orderStatus == 2) { //使用中
                closeTaocanOrder(sysTaocanOrder);
            } else if (orderStatus == 0) { //待付款
                //判断当前订单是否存在待支付的套餐超时费用
                Double overtimeFee = sysTaocanOrder.getOvertimeFee();
                if (Objects.nonNull(overtimeFee) && overtimeFee > 0) { //当前订单存在待支付的套餐超时费用
                    closeTaocanOrder(sysTaocanOrder);
                }
            }

        } else if (orderType == 2) { //拼团订单

        }
    }

    @Override
    public void stopOrder(int orderType, String orderId) {
        if (orderType == 0) { //计时收费订单
            SysJishiOrder sysJishiOrder = sysJishiOrderService.selectSysJishiOrderByOrderId(orderId);
            int orderStatus = sysJishiOrder.getOrderStatus();
            if (orderStatus == 2) { //使用中
                Date beginTime = sysJishiOrder.getBeginTime();
                if (Objects.nonNull(beginTime)) { //确保订单已开始计费.(如果订单已经开始计费，且状态仍然处于使用中则一定还未停止计费)
                    stopChargingForJishiOrder(sysJishiOrder);
                }
            }
        } else if (orderType == 1) { //套餐收费订单
            SysTaocanOrder sysTaocanOrder = sysTaocanOrderService.selectSysTaocanOrderByOrderId(orderId);
            int orderStatus = sysTaocanOrder.getOrderStatus();
            if (orderStatus == 2) { //使用中
                Date beginTime = sysTaocanOrder.getBeginTime();
                if (Objects.nonNull(beginTime)) { //确保订单已开始计费.(如果订单已经开始计费，且状态仍然处于使用中则一定还未停止计费)
                    stopChargingForTaocanOrder(sysTaocanOrder);
                }
            }

        } else if (orderType == 2) { //拼团订单

        }
    }

    private void stopChargingForTaocanOrder(SysTaocanOrder sysTaocanOrder) {
        //订单结束计费的时间
        Date endTime = new Date();
        Date beginTime = sysTaocanOrder.getBeginTime();
        //获取订单实际持续的收费时长，单位：毫秒。
        long realDuration = endTime.getTime() - beginTime.getTime();
        //套餐计费订单此时需要先判断实际计费时长是否查过当前套餐包含的时长，如果没有超则此时可以直接结束并关闭订单。如果超过了
        // 套餐时长则需要计算超过的时长，然后依据当前景区计时计费的规则来计算超出时长应补交的费用。
        //设置订单停止计费的时间
        sysTaocanOrder.setEndTime(endTime);
        //设置订单实际持续的收费时长
        sysTaocanOrder.setRealDuration(realDuration);
        //更新船只状态为“空闲”
        Long boatId = sysTaocanOrder.getBoatId();
        SysBoat sysBoat = sysBoatService.selectSysBoatByBoatId(boatId);
        if (sysBoat.getStatus() != BoatStatus.Offline.getCode()) { //只要当前船只状态不为"离线"状态就都应该更新船只状态为空闲。
            sysBoatService.updateBoatStatusByBoadId(boatId, BoatStatus.Idle);
        }

        //删除船只订单关联对象(此时删除是合适的，因为此时用户，船只，订单三者之间的关联绑定关系已解除，尤其是用户与船只之间的绑定关系已解除，该船只从此刻开始可以被其他用户下单使用。)
        Long userId = sysTaocanOrder.getUserId();
        userBoatOrderInfoService.deleteUserBoatOrderInfoByUserId(userId);

        //计算超时时长(单位：分钟)。注意：此处将毫秒值转为分钟，且不足一分钟的给舍弃掉了。
        int overtime = (int) (realDuration - sysTaocanOrder.getTaocanDuration() * 60 * 1000) / (60 * 1000);
        if (overtime <= 0) {
            overtime = 0;
        }
        sysTaocanOrder.setOvertime(overtime);

        String orderId = sysTaocanOrder.getOrderId();
        int orderType = sysTaocanOrder.getOrderType();

        //计算超时费用(单位：元)
        double overtimeFee = 0;
        TaocanOrderEndChargeMsg taocanOrderEndChargeMsg = new TaocanOrderEndChargeMsg();
        taocanOrderEndChargeMsg.setOrderId(orderId);
        taocanOrderEndChargeMsg.setOrderType(orderType);
        taocanOrderEndChargeMsg.setBeginTime(beginTime);
        taocanOrderEndChargeMsg.setEndTime(endTime);
        taocanOrderEndChargeMsg.setRealDuration(realDuration);
        taocanOrderEndChargeMsg.setTaocanDuration(sysTaocanOrder.getTaocanDuration()); //原套餐时长
        taocanOrderEndChargeMsg.setOvertime(overtime);
        //查找到当前订单所属小程序用户的WebSocketChannel
        Channel miniProgramChannel = WebSocketChannelManager.getMiniprogramWebsocketChannelByUserId(userId);
        if (realDuration > 60 * 1000) { //订单实际总计费时长超过1分钟
            if (overtime > 0) { //套餐订单超时，且超过1分钟(含)以上
                //将订单状态更新为“待付款”。注意：此时订单状态不能修改为“已完成”，因为此时订单超时费用还未支付，当前只是结束计费了，订单并未完成。
                sysTaocanOrder.setOrderStatus(OrderStatus.Unpaid.getCode());
                sysTaocanOrder.setOrderStatusDesc(OrderStatus.Unpaid.getInfo());

                //计算需要补交的订单超时费用
                //1,先获取当前景区的计时计费规则
//                double price = sysTaocanOrder.getJishiPrice(); //计价：元/分钟
                Double overtimeFeeRule = sysTaocanOrder.getOvertimeFeeRule(); //套餐超时计费规则，单位：元/分钟。
                if (Objects.nonNull(overtimeFeeRule)) {
                    overtimeFee = overtimeFeeRule * overtime;
                } else {
                    overtimeFee = 0;
                }

                //设置订单超时费用需支付金额及当前支付状态
                sysTaocanOrder.setOvertimeFee(overtimeFee);
                sysTaocanOrder.setOvertimeFeePayStatus(PayStatus.Unpaid.getCode());
                sysTaocanOrder.setOvertimeFeePayStatusDesc(PayStatus.Unpaid.getInfo());

                sysTaocanOrderService.updateSysTaocanOrder(sysTaocanOrder);

                //通知小程序客户端订单停止计费。
                taocanOrderEndChargeMsg.setOvertimeFee(overtimeFee);
                if (Objects.nonNull(miniProgramChannel)) {
                    WebSocketMessageHandler.notifyClientSideTaocanOrderStopCharging(miniProgramChannel, taocanOrderEndChargeMsg);
                } else {
                    //如果此时发现当前订单所属用户不在线，则需要将还未发送出去的消息缓存下来，待该用户下次成功登录小程序后再主动给其发送此消息
                    log.warn("当前订单所属用户[userId={}]ws未连接，发送订单停止计费消息失败，将该消息存储起来待该用户下次连接后重发！", userId);
                    WebSocketChannelManager.addWsMsgBean(userId, taocanOrderEndChargeMsg);
                }

            } else { //订单未超时或超时不足1分钟
                //结束并关闭套餐订单。
                MySocketChannelInboundHandler1.finishTaocanOrder(sysTaocanOrder, taocanOrderEndChargeMsg, miniProgramChannel);
            }
        } else { //订单总计费时长不足1分钟
            //结束并关闭套餐订单。
            MySocketChannelInboundHandler1.finishTaocanOrder(sysTaocanOrder, taocanOrderEndChargeMsg, miniProgramChannel);
        }

        //创建运营动态信息
        ScenicOperationDynamicInfo scenicOperationDynamicInfo = new ScenicOperationDynamicInfo();
        scenicOperationDynamicInfo.setOperationType(OperationType.Order_Closed);
        scenicOperationDynamicInfo.setScenicId(sysTaocanOrder.getScenicId());
        scenicOperationDynamicInfo.setBoatId(sysTaocanOrder.getBoatId());
        scenicOperationDynamicInfo.setBoatCode(sysTaocanOrder.getBoatCode());
        scenicOperationDynamicInfo.setOrderId(orderId);
        scenicOperationDynamicInfo.setOrderType(orderType);
        scenicOperationDynamicInfo.setUserId(userId);
        Date createTime = new Date();
        scenicOperationDynamicInfo.setCreateTime(createTime);
        IScenicOperationDynamicInfoService operationInfoService = SpringUtils.getBean(IScenicOperationDynamicInfoService.class);
        scenicOperationDynamicInfo.setContent(String.format("%s, %s船被后台操作主动结束当前套餐订单的计费!", sdf.format(createTime), sysTaocanOrder.getBoatCode()));
        operationInfoService.insertOperationInfo(scenicOperationDynamicInfo);

        //向景区监控中心发送运营动态信息
        WebSocketMessageHandler.sendMsgToScenicAdmin(sysTaocanOrder.getScenicId(), scenicOperationDynamicInfo);
    }

    private void stopChargingForJishiOrder(SysJishiOrder sysJishiOrder) {
        //1，更新计时计费订单的信息
        //获取订单开始的时间
        Date beginTime = sysJishiOrder.getBeginTime();
        //订单结束计费的时间
        Date endTime = new Date();
        //获取订单实际持续的收费时长，单位：毫秒。
        long realDuration = endTime.getTime() - beginTime.getTime();
        //计算计时计费订单的实际费用。
        double orderFee = MySocketChannelInboundHandler1.calculateJishiOrderFee(sysJishiOrder.getJishiId(), realDuration);

        //将订单状态更新为“待付款”。注意：此处订单状态不能修改为“已完成”，因为此时订单费用还未支付，当前只是结束计费了，订单并未完成。
        sysJishiOrder.setOrderStatus(OrderStatus.Unpaid.getCode());
        sysJishiOrder.setOrderStatusDesc(OrderStatus.Unpaid.getInfo());

        //设置订单停止计费的时间
        sysJishiOrder.setEndTime(endTime);

        //设置订单实际持续的收费时长
        sysJishiOrder.setRealDuration(realDuration);
        //设置订单费用金额
        sysJishiOrder.setOrderFee(orderFee);
        //更新计时计费订单
        sysJishiOrderService.updateSysJishiOrder(sysJishiOrder);

        //2，更新船只状态为“空闲”
        Long boatId = sysJishiOrder.getBoatId();
        SysBoat sysBoat = sysBoatService.selectSysBoatByBoatId(boatId);
        if (sysBoat.getStatus() != BoatStatus.Offline.getCode()) { //只要当前船只状态不为"离线"状态就都应该更新船只状态为空闲。
            sysBoatService.updateBoatStatusByBoadId(boatId, BoatStatus.Idle);
        }

        //3，删除船只订单关联对象(此时删除是合适的，因为此时用户，船只，订单三者之间的关联绑定关系已解除，尤其是用户与船只之间的绑定关系已解除，该船只从此刻开始可以被其他用户下单使用。)
        userBoatOrderInfoService.deleteUserBoatOrderInfoByUserId(sysJishiOrder.getUserId());

        //4, 创建并发送计时订单结束计费的消息给小程序端
        String orderId = sysJishiOrder.getOrderId();
        Integer orderType = sysJishiOrder.getOrderType();
        Long userId = sysJishiOrder.getUserId();
        JishiOrderEndChargeMsg jishiOrderEndChargeMsg = new JishiOrderEndChargeMsg();
        jishiOrderEndChargeMsg.setOrderType(orderType);
        jishiOrderEndChargeMsg.setOrderId(orderId);
        jishiOrderEndChargeMsg.setBeginTime(beginTime);
        jishiOrderEndChargeMsg.setEndTime(endTime);
        jishiOrderEndChargeMsg.setRealDuration(realDuration);
        jishiOrderEndChargeMsg.setOrderFee(orderFee);
        //查找到当前订单所属小程序用户的WebSocketChannel
        Channel miniProgramChannel = WebSocketChannelManager.getMiniprogramWebsocketChannelByUserId(userId);
        if (Objects.nonNull(miniProgramChannel)) {
            //通知小程序客户端停止当前计时计费订单的计费。
            WebSocketMessageHandler.notifyClientSideJishiOrderStopCharging(miniProgramChannel, jishiOrderEndChargeMsg);
        } else {
            //如果此时发现当前订单所属用户不在线，则需要将还未发送出去的消息缓存下来，待该用户下次成功登录小程序后再主动给其发送此消息
            log.warn("当前订单所属用户[userId={}]ws未连接，发送订单停止计费消息失败，将该消息存储起来待该用户下次连接后重发！", userId);
            WebSocketChannelManager.addWsMsgBean(userId, jishiOrderEndChargeMsg);
        }

        //5, 创建并发送运营动态信息给景区监控中心
        ScenicOperationDynamicInfo scenicOperationDynamicInfo = new ScenicOperationDynamicInfo();
        scenicOperationDynamicInfo.setOperationType(OperationType.Order_Closed);
        scenicOperationDynamicInfo.setScenicId(sysJishiOrder.getScenicId());
        scenicOperationDynamicInfo.setBoatId(sysJishiOrder.getBoatId());
        scenicOperationDynamicInfo.setBoatCode(sysJishiOrder.getBoatCode());
        scenicOperationDynamicInfo.setOrderId(orderId);
        scenicOperationDynamicInfo.setOrderType(orderType);
        scenicOperationDynamicInfo.setUserId(sysJishiOrder.getUserId());
        Date createTime = new Date();
        scenicOperationDynamicInfo.setCreateTime(createTime);
        IScenicOperationDynamicInfoService operationInfoService = SpringUtils.getBean(IScenicOperationDynamicInfoService.class);
        scenicOperationDynamicInfo.setContent(String.format("%s, %s船在后台操作主动结束计费!", sdf.format(createTime), sysJishiOrder.getBoatCode()));
        operationInfoService.insertOperationInfo(scenicOperationDynamicInfo);
        //向景区监控中心发送运营动态信息
        WebSocketMessageHandler.sendMsgToScenicAdmin(sysJishiOrder.getScenicId(), scenicOperationDynamicInfo);
    }


    /**
     * 关闭套餐订单。
     *
     * @param sysTaocanOrder
     */
    private void closeTaocanOrder(SysTaocanOrder sysTaocanOrder) {
        //1，首先更新数据库中当前订单的状态等信息
        //将订单状态修改为"已关闭"
        sysTaocanOrder.setOrderStatus(OrderStatus.Closed.getCode());
        sysTaocanOrder.setOrderStatusDesc(OrderStatus.Closed.getInfo());
        //设置订单关闭时间为当前时间
        sysTaocanOrder.setClosedTime(new Date());
        sysTaocanOrder.setRemark("后台操作关闭当前订单");
        sysTaocanOrderService.updateSysTaocanOrder(sysTaocanOrder);

        //2，删除用户船只订单三方信息关联对象
        Long userId = sysTaocanOrder.getUserId();
        userBoatOrderInfoService.deleteUserBoatOrderInfoByUserId(userId);

        //3, 主动给用户退还已支付的押金及套餐费用，
        String orderId = sysTaocanOrder.getOrderId();
        Integer orderType = sysTaocanOrder.getOrderType();
        WxRefundReqBody refundRequestBody = new WxRefundReqBody();
        refundRequestBody.setOrderId(orderId);
        refundRequestBody.setOrderType(orderType);
        refundRequestBody.setRefundType(Refund_Type.Refund_All.ordinal());
        refundRequestBody.setReason("后台主动关闭订单并退还已支付的押金及套餐费用");
        wxPayV3Service.refund(refundRequestBody);

        Date beginTime = sysTaocanOrder.getBeginTime();
        //判断当前套餐订单是否已开始计费，如果还未开始计费，则还需更新船只运营状态为"空闲"，并通知小程序端订单结束。
        if (Objects.isNull(beginTime)) { //订单还未开始计费，说明此时用户还未解锁船只
            //更新船只状态为“空闲”
            Long boatId = sysTaocanOrder.getBoatId();
            SysBoat sysBoat = sysBoatService.selectSysBoatByBoatId(boatId);
            if (sysBoat.getStatus() != BoatStatus.Offline.getCode()) { //只要当前船只状态不为"离线"状态就都应该更新船只状态为空闲。
                sysBoatService.updateBoatStatusByBoadId(boatId, BoatStatus.Idle);
            }

            //通知小程序端订单结束
            TaocanOrderEndChargeMsg taocanOrderEndChargeMsg = new TaocanOrderEndChargeMsg();
            taocanOrderEndChargeMsg.setOrderId(sysTaocanOrder.getOrderId());
//            taocanOrderEndChargeMsg.setBeginTime(beginTime);
//            taocanOrderEndChargeMsg.setEndTime(endTime);
            taocanOrderEndChargeMsg.setRealDuration(0L);
            taocanOrderEndChargeMsg.setTaocanDuration(sysTaocanOrder.getTaocanDuration());
            taocanOrderEndChargeMsg.setOvertime(0);
            taocanOrderEndChargeMsg.setOvertimeFee(0);

            //查找到当前订单所属小程序用户的WebSocketChannel
            Channel miniProgramChannel = WebSocketChannelManager.getMiniprogramWebsocketChannelByUserId(userId);
            if (Objects.nonNull(miniProgramChannel)) {
                //通知小程序客户端停止当前计时计费订单的计费。
                WebSocketMessageHandler.notifyClientSideTaocanOrderStopCharging(miniProgramChannel, taocanOrderEndChargeMsg);
            } else {
                //如果此时发现当前订单所属用户不在线，则需要将还未发送出去的消息缓存下来，待该用户下次成功登录小程序后再主动给其发送此消息
                log.warn("当前订单所属用户[userId={}]ws未连接，发送订单停止计费消息失败，将该消息存储起来待该用户下次连接后重发！", userId);
                WebSocketChannelManager.addWsMsgBean(userId, taocanOrderEndChargeMsg);
            }

        } else { //订单已开始计费
            //此时订单应该也已经停止计费，且还存在待支付的超时费用，但此时已经无需再做进一步处理，因为当前订单用户已支付的押金和套餐费已全部退还，订单超时费用也将不再收取
        }
    }

    /**
     * 关闭使用中且还未开始计费的计时订单。
     *
     * @param sysJishiOrder
     * @param isChargingEnd 订单是否已经结束计费。
     */
    private void closeJishiOrder(SysJishiOrder sysJishiOrder, boolean isChargingEnd) {
        //1，首先更新数据库中当前订单的状态等信息
        //将订单状态修改为"已关闭"
        sysJishiOrder.setOrderStatus(OrderStatus.Closed.getCode());
        sysJishiOrder.setOrderStatusDesc(OrderStatus.Closed.getInfo());
        //设置订单关闭时间为当前时间
        sysJishiOrder.setClosedTime(new Date());
        sysJishiOrder.setRemark("后台操作关闭当前订单");
        sysJishiOrderService.updateSysJishiOrder(sysJishiOrder);

        //2，删除用户船只订单三方信息关联对象
        Long userId = sysJishiOrder.getUserId();
        userBoatOrderInfoService.deleteUserBoatOrderInfoByUserId(userId);

        //4, 主动给用户退还已支付的押金
        String orderId = sysJishiOrder.getOrderId();
        Integer orderType = sysJishiOrder.getOrderType();
        WxRefundReqBody refundRequestBody = new WxRefundReqBody();
        refundRequestBody.setOrderId(orderId);
        refundRequestBody.setOrderType(orderType);
        refundRequestBody.setRefundType(Refund_Type.Refund_Deposit.ordinal());
        refundRequestBody.setReason("后台主动关闭订单并退还已支付的押金");
        wxPayV3Service.refund(refundRequestBody);

        //还未结束计费的订单还需更新船只运营状态为"空闲"，并通知小程序端停止计费。但已经结束计费的订单无需执行这些操作。
        if (!isChargingEnd) {
            //3，更新船只状态为“空闲”
            Long boatId = sysJishiOrder.getBoatId();
            SysBoat sysBoat = sysBoatService.selectSysBoatByBoatId(boatId);
            if (sysBoat.getStatus() != BoatStatus.Offline.getCode()) { //只要当前船只状态不为"离线"状态就都应该更新船只状态为空闲。
                sysBoatService.updateBoatStatusByBoadId(boatId, BoatStatus.Idle);
            }

            //创建并发送计时订单已结束的消息给小程序端
            JishiOrderEndChargeMsg jishiOrderEndChargeMsg = new JishiOrderEndChargeMsg();
            jishiOrderEndChargeMsg.setOrderType(orderType);
            jishiOrderEndChargeMsg.setOrderId(orderId);
//        jishiOrderEndChargeMsg.setBeginTime(beginTime);
//        jishiOrderEndChargeMsg.setEndTime(endTime);
            jishiOrderEndChargeMsg.setRealDuration(0L);
            jishiOrderEndChargeMsg.setOrderFee(0);
            //查找到当前订单所属小程序用户的WebSocketChannel
            Channel miniProgramChannel = WebSocketChannelManager.getMiniprogramWebsocketChannelByUserId(userId);
            if (Objects.nonNull(miniProgramChannel)) {
                //通知小程序客户端停止当前计时计费订单的计费。
                WebSocketMessageHandler.notifyClientSideJishiOrderStopCharging(miniProgramChannel, jishiOrderEndChargeMsg);
            } else {
                //如果此时发现当前订单所属用户不在线，则需要将还未发送出去的消息缓存下来，待该用户下次成功登录小程序后再主动给其发送此消息
                log.warn("当前订单所属用户[userId={}]ws未连接，发送订单停止计费消息失败，将该消息存储起来待该用户下次连接后重发！", userId);
                WebSocketChannelManager.addWsMsgBean(userId, jishiOrderEndChargeMsg);
            }
        }
    }
}
